//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <errno.h>
#include <define.h>
#include <serialio.h>
#include <control.h>
#include "bulverde.h"

static bool bvd_serial_init(struct devtab_entry *tab);
static SERIAL_ErrNo bvd_serial_lookup(struct devtab_entry **tab,
                                   struct devtab_entry *sub_tab,
                                   const char *name);
// Enable uart
static bool bvd_serial_open(serial_channel *chan);
// Disable uart
static bool bvd_serial_close(serial_channel *chan);

static bool bvd_serial_putc(serial_channel *chan, unsigned char c);
static bool bvd_serial_getc(serial_channel *chan, unsigned char *c);
static SERIAL_ErrNo bvd_serial_set_config(serial_channel *chan, UINT32 key,
                                       const void *xbuf, UINT32 *len);
static void bvd_serial_start_xmit(serial_channel *chan);
static void bvd_serial_stop_xmit(serial_channel *chan);
static int  bvd_serial_ISR(serial_channel *chan);

static SERIAL_FUNS(
    bvd_serial_funs,
    bvd_serial_open,
    bvd_serial_close,
    bvd_serial_putc,
    bvd_serial_getc,
    bvd_serial_set_config,
    bvd_serial_start_xmit,
    bvd_serial_stop_xmit,
    bvd_serial_ISR
);


// config special serial port

port_tbl_t g_port_tbl = {
    3,
    {
        {BVDC_FFUART, 22},
        {BVDC_BTUART, 21},
        {BVDC_STUART, 20},
    },
};

bool serial_devtab_res_init(
    devtab_entry * tab,
    unsigned int port_base,
    int irq)
{
    assert(tab);
    serial_channel *channel = new serial_channel;
    if (!channel) { return false; }

    bvd_serial_info *info = new bvd_serial_info;
    if (!info) { return false; }
    memset(info, 0, sizeof(bvd_serial_info));

    info->base = port_base;
    info->int_num = irq;

    channel->dev_priv = info;

    tab->handlers = &g_serial_devio;
    tab->init = bvd_serial_init;
    tab->lookup = bvd_serial_lookup;
    tab->priv = channel;

    bvd_serial_init(tab);

    return true;
}

void
techfaith_set_gpio(SERIAL_ADDRWORD base)
{
    unsigned char _mcr;

    GAFR1_L |= (1<<4)|(1<<6)|(1<<10)|(2<<14)|(2<<16)|(2<<18);
    GAFR1_L &= ~(3<<12);
    GPDR1 |= (1<<7)|(1<<8)|(1<<9);
    GPDR1 &= ~((1<<2)|(1<<3)|(1<<5)|(1<<6));

    HAL_READ_UINT8(base + BVDC_UART_MCR, _mcr);
    _mcr &= ~(1 << 0);    // disable dsr
    HAL_WRITE_UINT8(base + BVDC_UART_MCR, _mcr);
    DzDelay(20 * 1000);
    HAL_READ_UINT8(base + BVDC_UART_MCR, _mcr);
    _mcr |= (1 << 0);     // enable dsr
    HAL_WRITE_UINT8(base + BVDC_UART_MCR, _mcr);
}

// Internal function to actually configure the hardware to desired
// baud rate, etc.
static bool
serial_config_port(serial_channel *chan,
                   SERIAL_INFO *new_config, bool init)
{
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;
    unsigned short baud_divisor = select_baud[new_config->baud];
    unsigned char _lcr, _ier, _mcr;

    if (baud_divisor == 0) return(false);  // Invalid configuration

    // Disable port interrupts while changing hardware
    HAL_READ_UINT8(base + BVDC_UART_IER, _ier);
    HAL_WRITE_UINT8(base + BVDC_UART_IER, 0);

    _lcr = select_word_length[new_config->word_length -
                              NUM_SERIAL_WORD_LENGTH_5] |
        select_stop_bits[new_config->stop] |
        select_parity[new_config->parity];
    HAL_WRITE_UINT8(base + BVDC_UART_LCR, _lcr | LCR_DLAB);
    HAL_WRITE_UINT8(base + BVDC_UART_DLH, baud_divisor >> 8);
    HAL_WRITE_UINT8(base + BVDC_UART_DLL, baud_divisor & 0xFF);
    HAL_WRITE_UINT8(base + BVDC_UART_LCR, _lcr);
    if (init) {

#ifdef SERIALPKG_IO_SERIAL_BULVERDE_FIFO
        unsigned char _fcr_thresh;

        switch (SERIALPKG_IO_SERIAL_BULVERDE_FIFO_RX_THRESHOLD) {
        case 1:
            _fcr_thresh = FCR_RX_INTR_LEVEL_1; break;
        case 8:
            _fcr_thresh = FCR_RX_INTR_LEVEL_8; break;
        case 16:
            _fcr_thresh = FCR_RX_INTR_LEVEL_16; break;
        case 32:
            _fcr_thresh = FCR_RX_INTR_LEVEL_32; break;
        default :
            _fcr_thresh = FCR_RX_INTR_LEVEL_1; break;
        }
        _fcr_thresh |= FCR_TXRXFIFO_ENABLE |
            FCR_RESET_RCVR_FIFO |
            FCR_RESET_TXMT_FIFO;
        HAL_WRITE_UINT8(base + BVDC_UART_FCR,
                        _fcr_thresh); // Enable and clear FIFO
#endif // FIFO

        // IER
        if (chan->out_cbuf.len != 0) {
            _ier = (IER_RAVIE | IER_RTOIE);
        }
        else {
            _ier = 0;
        }
    }

    // Modem interrupt enable
    _mcr = MCR_IRQ_ENABLE | MCR_AUTO_FLOW_ENABLE_CTS;
    if (new_config->flags & NUM_SERIAL_FLOW_RTSCTS_RX) {
        _mcr |= MCR_RTS;
    }
    if (new_config->flags & NUM_SERIAL_FLOW_DSRDTR_RX) {
        _mcr |= MCR_DTR;
    }
    if (new_config->flags & NUM_SERIAL_FLOW_RTSCTS_TX) {
        _mcr |= MCR_AUTO_FLOW_ENABLE_CTS;
    }
    HAL_WRITE_UINT8(base + BVDC_UART_MCR, _mcr);

    // IER
    if (chan->support_line_status) {
        _ier |= (IER_RLSE | IER_MIE);
    }
    HAL_WRITE_UINT8(base + BVDC_UART_IER, IER_UUE | _ier);

    if (new_config != &chan->config) {
        chan->config = *new_config;
    }

#if 0
    Dprintf(L"SERIAL config: %d, %d, %d, %d, %d, %d\n",
            new_config->baud,
            new_config->flags,
            new_config->parity,
            new_config->stop,
            new_config->word_length, ser_chan->deviceType);
#endif

    return(true);
}

// Function to initialize the device.  Called at bootstrap time.
static bool bvd_serial_init(struct devtab_entry *tab)
{
    serial_channel *chan = (serial_channel *)tab->priv;
    assert(chan);
    assert(chan->dev_priv);

    bvd_serial_info *ser_chan;

    // initialize

    chan->funs = &bvd_serial_funs;
    chan->callbacks = &g_serial_callbacks;

    // sub-config
    chan->config.baud = SERIAL_BAUD_RATE(115200);
    chan->config.stop = SERIAL_STOP_DEFAULT;
    chan->config.parity = SERIAL_PARITY_DEFAULT;
    chan->config.word_length = SERIAL_WORD_LENGTH_DEFAULT;
    chan->config.flags = SERIAL_FLAGS_DEFAULT;

    chan->support_flow_control = true;
    chan->flow_desc.flowctrl_software = false;
    chan->support_line_status = true;

    chan->bufs_init = false;

    // sub-config
#if SERIAL_ARM_BULVERDE_SERIAL_OUT_BUFSIZE > 0
    chan->out_cbuf.data =
        (unsigned char *) malloc(SERIAL_ARM_BULVERDE_SERIAL_OUT_BUFSIZE);
    if (!chan->out_cbuf.data) {
        return false;
    }
    memset(chan->out_cbuf.data,
           0,
           SERIAL_ARM_BULVERDE_SERIAL_OUT_BUFSIZE);
    chan->out_cbuf.put = 0;
    chan->out_cbuf.get = 0;
    chan->out_cbuf.len = SERIAL_ARM_BULVERDE_SERIAL_OUT_BUFSIZE;
#endif // SERIAL_ARM_BULVERDE_SERIAL_OUT_BUFSIZE

#if SERIAL_ARM_BULVERDE_SERIAL_IN_BUFSIZE > 0
    chan->in_cbuf.data =
        (unsigned char *) malloc(SERIAL_ARM_BULVERDE_SERIAL_IN_BUFSIZE);
    if (!chan->in_cbuf.data) {
        return false;
    }
    memset(chan->in_cbuf.data,
           0,
           SERIAL_ARM_BULVERDE_SERIAL_IN_BUFSIZE);
    chan->in_cbuf.put = 0;
    chan->in_cbuf.get = 0;
    chan->in_cbuf.len = SERIAL_ARM_BULVERDE_SERIAL_IN_BUFSIZE;
#endif // SERIAL_ARM_BULVERDE_SERIAL_IN_BUFSIZE

    ser_chan = (bvd_serial_info *) chan->dev_priv;

#ifdef SERIALDBG_IO_INIT
    kprintf("Bulverde SERIAL init - dev: %X.%d\n",
            ser_chan->base, ser_chan->int_num);
#endif
    // Really only required for interrupt driven devices
    (chan->callbacks->serial_init)(chan);

    techfaith_set_gpio(ser_chan->base);
    serial_config_port(chan, &chan->config, true);

    return(true);
}

// This routine is called when the device is "looked" up (i.e. attached)
static SERIAL_ErrNo
bvd_serial_lookup(struct devtab_entry **tab,
                  struct devtab_entry *sub_tab,
                  const char *name)
{
    serial_channel *chan = (serial_channel *)(*tab)->priv;

    // Really only required for interrupt driven devices
    (chan->callbacks->serial_init)(chan);
    return(ENOERR);
}

// Enable uart
static bool
bvd_serial_open(serial_channel *chan)
{
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;
    UINT8 _mcr;

    HAL_READ_UINT8(base + BVDC_UART_MCR, _mcr);
    HAL_WRITE_UINT8(base + BVDC_UART_MCR, _mcr | MCR_IRQ_ENABLE);
    chan->extension.device_opened = true;
    return true;
}

// Disable uart
static bool
bvd_serial_close(serial_channel *chan)
{
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;
    UINT8 _mcr;
    HAL_READ_UINT8(base + BVDC_UART_MCR, _mcr);
    HAL_WRITE_UINT8(base + BVDC_UART_MCR, _mcr & ~MCR_IRQ_ENABLE);
    chan->extension.device_opened = false;
    return true;
}

// Send a character to the device output buffer.
// Return 'true' if character is sent to device
static bool
bvd_serial_putc(serial_channel *chan, unsigned char c)
{
    UINT8 _lsr;
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;

    HAL_READ_UINT8(base + BVDC_UART_LSR, _lsr);
    if (_lsr & LSR_TDRQ) {
        // The transmit FIFO has half or less than half data.
        HAL_WRITE_UINT8(base + BVDC_UART_THR, c);
        return(true);
    }
    // No space
    return(false);
}

// Fetch a character from the device input buffer, waiting if necessary
static bool
bvd_serial_getc(serial_channel *chan, unsigned char *c)
{
    UINT8 _lsr;
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;

    // Wait for char
    HAL_READ_UINT8(base + BVDC_UART_LSR, _lsr);
    if (_lsr & LSR_DR) {
        HAL_READ_UINT8(base + BVDC_UART_RBR, *c);

        return true;
    }

    return false;
}

// Set up the device characteristics; baud rate, etc.
static SERIAL_ErrNo
bvd_serial_set_config(serial_channel *chan, UINT32 key, const void *xbuf,
                      UINT32 *len)
{
    switch (key) {
    case IO_SET_CONFIG_SERIAL_INFO:
        {
            SERIAL_INFO *config = (SERIAL_INFO *)xbuf;
            if (*len < sizeof(SERIAL_INFO)) {
                return(-EINVAL);
            }
            *len = sizeof(SERIAL_INFO);
            if (true != serial_config_port(chan, config, true))
                return(-EINVAL);
        }
        break;

    case IO_SET_CONFIG_SERIAL_HW_RX_FLOW_THROTTLE:
        {
            UINT8 _mcr;
            bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
            SERIAL_ADDRWORD base = ser_chan->base;
            UINT32 *f = (UINT32 *)xbuf;
            unsigned char mask=0;
            if (*len < sizeof(*f)) {
                return(-EINVAL);
            }

            if (chan->config.flags & NUM_SERIAL_FLOW_RTSCTS_RX) {
                mask = MCR_RTS;
            }
            if (chan->config.flags & NUM_SERIAL_FLOW_DSRDTR_RX) {
                mask |= MCR_DTR;
            }

            HAL_READ_UINT8(base + BVDC_UART_MCR, _mcr);
            if (*f) { // we should throttle
                _mcr &= ~mask;
            }
            else { // we should no longer throttle
                _mcr |= mask;
            }
            HAL_WRITE_UINT8(base + BVDC_UART_MCR, _mcr);
        }
        break;

    case IO_SET_CONFIG_SERIAL_HW_FLOW_CONFIG:
        // Nothing to do because we do support both RTSCTS and DSRDTR flow
        // control.
        // Other targets would clear any unsupported flags here and
        // would then return -ENOSUPP - the higher layer can then query
        // what flags are set and decide what to do. This is optimised for
        // the most common case - i.e. that authors know what their hardware
        // is capable of.
        // We just return ENOERR.
        break;

    // the follows are get method!
    case IO_GET_CONFIG_SERIAL_MODEM_STATUS: // notice: get method
        {
            bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
            SERIAL_ADDRWORD base = ser_chan->base;
            UINT32 *states = (UINT32 *)xbuf;
            UINT8 _msr;
            unsigned char mask=0;

            if (*len < sizeof(*states)) {
                return(-EINVAL);
            }

            HAL_READ_UINT8(base + BVDC_UART_MSR, _msr);
            if (_msr & MSR_DSR) {
                mask |= NUM_DSR_ON;
            }
            if (_msr & MSR_CTS) {
                mask |= NUM_CTS_ON;
            }
            if (_msr & MSR_RI) {
                mask |= NUM_RING_ON;
            }
            if (chan->support_line_status) {
                mask |= NUM_RLSD_ON;
            }
            *states = (UINT32) mask;
        }
        break;

    default:
        return(-EINVAL);
    }
    return(ENOERR);
}

// Enable the transmitter on the device
static void
bvd_serial_start_xmit(serial_channel *chan)
{
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;
    UINT8 _ier;

    HAL_READ_UINT8(base + BVDC_UART_IER, _ier);
    _ier |= IER_TIE;            // Enable xmit interrupt
    HAL_WRITE_UINT8(base + BVDC_UART_IER, _ier);
}

// Disable the transmitter on the device
static void
bvd_serial_stop_xmit(serial_channel *chan)
{
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;
    UINT8 _ier;

    HAL_READ_UINT8(base + BVDC_UART_IER, _ier);
    _ier &= ~IER_TIE;           // Disable xmit interrupt
    HAL_WRITE_UINT8(base + BVDC_UART_IER, _ier);
}

// Serial I/O - interrupt handler (ISR)
static int
bvd_serial_ISR(serial_channel *chan)
{
    bvd_serial_info *ser_chan = (bvd_serial_info *)chan->dev_priv;
    SERIAL_ADDRWORD base = ser_chan->base;
    UINT8 _isr;
    UINT8 _mask;

    // Check if we have an interrupt pending - note that the interrupt
    // is pending of the low bit of the isr is *0*, not 1.
    HAL_READ_UINT8(base + BVDC_UART_IIR, _isr);
    while ((_isr & IIR_NO_INTERRUPT_PENDING) == 0) {
        _mask = _isr & IIR_INT_MASK;

        if (_mask == IIR_RDA || _mask == IIR_CTI) {
            unsigned char c;

            while (chan->funs->getc(chan, &c)) {
                (chan->callbacks->rcv_char)(chan, c);
            }
        }
        else if (_mask == IIR_THRE) {
            (chan->callbacks->xmt_char)(chan);
        }
        else if (chan->support_line_status) { // other masks

            if (_mask == IIR_RLS) {
                serial_line_status_t stat;
                UINT8 _lsr;

                HAL_READ_UINT8(base + BVDC_UART_LSR, _lsr);

                // this might look expensive, but it is rarely the case that
                // more than one of these is set
                stat.value = 1;
                if (_lsr & LSR_OE) {
                    stat.which = NUM_SERIAL_STATUS_OVERRUNERR;
                    (chan->callbacks->indicate_status)(chan, &stat );
                }
                if (_lsr & LSR_PE) {
                    stat.which = NUM_SERIAL_STATUS_PARITYERR;
                    (chan->callbacks->indicate_status)(chan, &stat );
                }
                if (_lsr & LSR_FE) {
                    stat.which = NUM_SERIAL_STATUS_FRAMEERR;
                    (chan->callbacks->indicate_status)(chan, &stat );
                }
                if (_lsr & LSR_BI) {
                    stat.which = NUM_SERIAL_STATUS_BREAK;
                    (chan->callbacks->indicate_status)(chan, &stat );
                }
            }
            else if (_mask == IIR_MS) {
                serial_line_status_t stat;
                UINT8 _msr;

                HAL_READ_UINT8(base + BVDC_UART_MSR, _msr);

                // SERIALOPT_IO_SERIAL_FLOW_CONTROL_HW
                if (_msr & MSR_DDSR) {
                    if (chan->config.flags & NUM_SERIAL_FLOW_DSRDTR_TX) {
                        stat.which = NUM_SERIAL_STATUS_FLOW;
                        stat.value = (0 != (_msr & MSR_DSR));
                        (chan->callbacks->indicate_status)(chan, &stat );
                    }
                }
                if (_msr & MSR_DCTS) {
                    if (chan->config.flags & NUM_SERIAL_FLOW_RTSCTS_TX) {
                        stat.which = NUM_SERIAL_STATUS_FLOW;
                        stat.value = (0 != (_msr & MSR_CTS));
                        (chan->callbacks->indicate_status)(chan, &stat );
                    }
                }

                if (_msr & MSR_DDCD) {
                    stat.which = NUM_SERIAL_STATUS_CARRIERDETECT;
                    stat.value = (0 != (_msr & MSR_DCD));
                    (chan->callbacks->indicate_status)(chan, &stat );
                }
                if (_msr & MSR_RI) {
                    stat.which = NUM_SERIAL_STATUS_RINGINDICATOR;
                    stat.value = 1;
                    (chan->callbacks->indicate_status)(chan, &stat );
                }
                if (_msr & MSR_TERI) { // the trailing edge ring indicator
                    stat.which = NUM_SERIAL_STATUS_RINGINDICATOR;
                    stat.value = 0;
                    (chan->callbacks->indicate_status)(chan, &stat );
                }
            }
            else if (_mask == IIR_CTI_2) {
#ifdef SERIALPKG_IO_SERIAL_BULVERDE_FIFO
                // Reset Receive FIFO

                unsigned char _fcr_thresh;

                switch (SERIALPKG_IO_SERIAL_BULVERDE_FIFO_RX_THRESHOLD) {
                case 1:
                    _fcr_thresh = FCR_RX_INTR_LEVEL_1; break;
                case 8:
                    _fcr_thresh = FCR_RX_INTR_LEVEL_8; break;
                case 16:
                    _fcr_thresh = FCR_RX_INTR_LEVEL_16; break;
                case 32:
                    _fcr_thresh = FCR_RX_INTR_LEVEL_32; break;
                default :
                    _fcr_thresh = FCR_RX_INTR_LEVEL_1; break;
                }
                _fcr_thresh |= (FCR_TXRXFIFO_ENABLE | FCR_RESET_RCVR_FIFO);
                HAL_WRITE_UINT8(base + BVDC_UART_FCR,
                                _fcr_thresh); // Enable and clear recv FIFO
#endif // FIFO
            }
            else {
                // Yes, this assertion may well not be visible. *But*
                // if debugging, we may still successfully hit a breakpoint
                // on assert_fail, which _is_ useful
                SERIAL_FAIL("unhandled serial interrupt state");
            }
        }
        else {
            // omits mask!, nothing to do
        }

        HAL_READ_UINT8(base + BVDC_UART_IIR, _isr);
    } // while

    return 0;
}

// EOF bulverde.cpp

